package com.gateway.bootstrap.listener;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.gateway.bootstrap.convert.ApiDataConvertFunction;
import com.gateway.bootstrap.convert.PluginDataConvertFunction;
import com.gateway.bootstrap.convert.RouteDataConvertFunction;
import com.gateway.common.dto.RuleData;
import com.gateway.common.dto.SelectorData;
import com.gateway.common.dto.route.FilterData;
import com.gateway.common.dto.route.PredicateData;
import com.gateway.common.dto.route.RouteData;
import com.gateway.common.enums.CommonStatusEnum;
import com.gateway.common.enums.DataEventTypeEnum;
import com.gateway.common.enums.DeletedEnum;
import com.gateway.plugin.base.cache.CommonPluginDataSubscriber;
import com.gateway.plugin.base.convert.RouteDataConvert;
import com.gateway.plugin.base.handler.ApiDataHandler;
import com.gateway.plugin.base.handler.event.HandlerRefreshEvent;
import com.gateway.repository.bean.GatewayApi;
import com.gateway.repository.bean.GatewayFilterDefinition;
import com.gateway.repository.bean.GatewayPlugin;
import com.gateway.repository.bean.GatewayPluginRule;
import com.gateway.repository.bean.GatewayPluginSelector;
import com.gateway.repository.bean.GatewayPredicateDefinition;
import com.gateway.repository.bean.GatewayRouteDefinition;
import com.gateway.repository.bean.RuleCondition;
import com.gateway.repository.bean.SelectorCondition;
import com.gateway.repository.service.GatewayApiService;
import com.gateway.repository.service.GatewayFilterDefinitionService;
import com.gateway.repository.service.GatewayPluginRuleService;
import com.gateway.repository.service.GatewayPluginSelectorService;
import com.gateway.repository.service.GatewayPluginService;
import com.gateway.repository.service.GatewayPredicateDefinitionService;
import com.gateway.repository.service.GatewayRouteDefinitionService;
import com.gateway.repository.service.RuleConditionService;
import com.gateway.repository.service.SelectorConditionService;
import com.gateway.router.base.handler.RouteDataHandler;
import com.gateway.router.base.handler.RouteFilterDataHandler;
import com.gateway.router.base.handler.RoutePredicateDataHandler;
import com.google.common.collect.Lists;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @program: api-gateway
 * @description:
 * @author: YuKai Fan
 * @create: 2025/2/19 20:12
 **/
@Component
public class HandlerRefreshListener implements ApplicationListener<HandlerRefreshEvent> {
    private static final Logger log = LoggerFactory.getLogger(HandlerRefreshListener.class);

    @Value("${spring.profiles.active}")
    private String env;

    @Resource
    private GatewayApiService gatewayApiService;
    @Resource
    private GatewayRouteDefinitionService gatewayRouteDefinitionService;
    @Resource
    private GatewayFilterDefinitionService gatewayFilterDefinitionService;
    @Resource
    private GatewayPredicateDefinitionService gatewayPredicateDefinitionService;
    @Resource
    private GatewayPluginService gatewayPluginService;
    @Resource
    private GatewayPluginSelectorService gatewayPluginSelectorService;
    @Resource
    private GatewayPluginRuleService gatewayPluginRuleService;
    @Resource
    private SelectorConditionService selectorConditionService;
    @Resource
    private RuleConditionService ruleConditionService;

    private final Map<String, RouteFilterDataHandler> filterHandlerMap;
    private final Map<String, RoutePredicateDataHandler> predicateHandlerMap;

    public HandlerRefreshListener(
            final List<RouteFilterDataHandler> routeFilterDataHandlerList,
            final List<RoutePredicateDataHandler> routePredicateDataHandlerList) {
        this.filterHandlerMap = routeFilterDataHandlerList.stream().collect(Collectors.toConcurrentMap(RouteFilterDataHandler::filterName, e -> e));
        this.predicateHandlerMap = routePredicateDataHandlerList.stream().collect(Collectors.toConcurrentMap(RoutePredicateDataHandler::predicateName, e -> e));
    }



    @Override
    public void onApplicationEvent(HandlerRefreshEvent event) {
        Object source = event.getSource();
        if (source instanceof ApiDataHandler apiDataHandler) {
            log.info("start refresh api data");
            doRefreshApi(apiDataHandler);
        }

        if (source instanceof CommonPluginDataSubscriber pluginDataSubscriber) {
            log.info("start refresh plugin data");
            doRefreshPluginAndSelectorAndRule(pluginDataSubscriber);
        }

        if (source instanceof RouteDataHandler routeDataHandler) {
            log.info("start refresh route data");
            doRefreshRoute(routeDataHandler);
        }
    }

    private void doRefreshApi(ApiDataHandler apiDataHandler) {
        List<GatewayApi> gatewayApiList = gatewayApiService.list(
                new QueryWrapper<GatewayApi>().lambda()
                        .eq(GatewayApi::getEnv, env)
                        .eq(GatewayApi::getRpcType, apiDataHandler.rpcType())
                        .eq(GatewayApi::getDeleted, DeletedEnum.NO.value)
        );
        if (CollectionUtils.isEmpty(gatewayApiList)) {
            return;
        }
        gatewayApiList.stream()
                .map(ApiDataConvertFunction::convert)
                .collect(Collectors.toSet()).forEach(apiDataHandler::handle);
    }

    private void doRefreshPluginAndSelectorAndRule(CommonPluginDataSubscriber pluginDataSubscriber) {
        List<GatewayPlugin> gatewayPluginList = gatewayPluginService.list(
                new QueryWrapper<GatewayPlugin>().lambda()
                        .eq(GatewayPlugin::getEnv, env)
                        .eq(GatewayPlugin::getDeleted, DeletedEnum.NO.value)
        );
        if (CollectionUtils.isEmpty(gatewayPluginList)) {
            return;
        }
        pluginDataSubscriber.refreshPluginDataAll();
        gatewayPluginList.stream()
                .map(PluginDataConvertFunction::convert)
                .collect(Collectors.toSet()).forEach(pluginData -> pluginDataSubscriber.subscribeDataHandler(pluginData, DataEventTypeEnum.UPDATE));

        List<GatewayPluginSelector> selectorList = gatewayPluginSelectorService.list(
                new QueryWrapper<GatewayPluginSelector>().lambda()
                        .eq(GatewayPluginSelector::getEnv, env)
                        .eq(GatewayPluginSelector::getDeleted, DeletedEnum.NO.value)
        );
        if (CollectionUtils.isEmpty(selectorList)) {
            return;
        }
        Set<Long> selectorIdSet = selectorList.stream().map(GatewayPluginSelector::getId).collect(Collectors.toSet());
        List<SelectorCondition> conditionList = selectorConditionService.list(
                new QueryWrapper<SelectorCondition>().lambda()
                        .in(SelectorCondition::getSelectorId, selectorIdSet)
                        .eq(SelectorCondition::getDeleted, DeletedEnum.NO.value)
        );
        pluginDataSubscriber.refreshSelectorDataAll();
        Map<Long, List<SelectorCondition>> selectorConditionMap = conditionList.stream().collect(Collectors.groupingBy(SelectorCondition::getSelectorId));
        for (GatewayPluginSelector gatewayPluginSelector : selectorList) {
            List<SelectorCondition> conditions = selectorConditionMap.get(gatewayPluginSelector.getId());
            SelectorData selectorData = PluginDataConvertFunction.convert(gatewayPluginSelector, conditions);
            pluginDataSubscriber.subscribeDataHandler(selectorData, DataEventTypeEnum.UPDATE);
        }

        List<GatewayPluginRule> ruleList = gatewayPluginRuleService.list(
                new QueryWrapper<GatewayPluginRule>().lambda()
                        .eq(GatewayPluginRule::getEnv, env)
                        .eq(GatewayPluginRule::getDeleted, DeletedEnum.NO.value)
        );
        Set<Long> ruleIdSet = ruleList.stream().map(GatewayPluginRule::getId).collect(Collectors.toSet());
        List<RuleCondition> ruleConditionList = ruleConditionService.list(
                new QueryWrapper<RuleCondition>().lambda()
                        .in(RuleCondition::getRuleId, ruleIdSet)
                        .eq(RuleCondition::getDeleted, DeletedEnum.NO.value)
        );
        pluginDataSubscriber.refreshRuleDataAll();
        Map<Long, List<RuleCondition>> ruleConditionMap = ruleConditionList.stream().collect(Collectors.groupingBy(RuleCondition::getRuleId));
        for (GatewayPluginRule gatewayPluginRule : ruleList) {
            List<RuleCondition> conditions = ruleConditionMap.get(gatewayPluginRule.getId());
            RuleData ruleData = PluginDataConvertFunction.convert(gatewayPluginRule, conditions);
            pluginDataSubscriber.subscribeDataHandler(ruleData, DataEventTypeEnum.UPDATE);
        }
    }

    private void doRefreshRoute(RouteDataHandler routeDataHandler) {
        List<GatewayRouteDefinition> gatewayRouteDefinitions = gatewayRouteDefinitionService.list(
                new QueryWrapper<GatewayRouteDefinition>().lambda()
                        .eq(GatewayRouteDefinition::getEnv, env)
                        .eq(GatewayRouteDefinition::getRpcType, routeDataHandler.rpcType())
                        .eq(GatewayRouteDefinition::getStatus, CommonStatusEnum.ONLINE.getStatus())
                        .eq(GatewayRouteDefinition::getDeleted, DeletedEnum.NO.value)
        );
        if (CollectionUtils.isEmpty(gatewayRouteDefinitions)) {
            return;
        }
        List<String> routeIdList = gatewayRouteDefinitions.stream().map(GatewayRouteDefinition::getRouteId).toList();
        List<GatewayFilterDefinition> gatewayFilterDefinitions = gatewayFilterDefinitionService.list(
                new QueryWrapper<GatewayFilterDefinition>().lambda()
                        .eq(GatewayFilterDefinition::getEnv, env)
                        .in(GatewayFilterDefinition::getRouteId, routeIdList)
                        .eq(GatewayFilterDefinition::getStatus, CommonStatusEnum.ONLINE.getStatus())
                        .eq(GatewayFilterDefinition::getDeleted, DeletedEnum.NO.value)
        );
        if (CollectionUtils.isEmpty(gatewayFilterDefinitions)) {
            return;
        }
        List<GatewayPredicateDefinition> gatewayPredicateDefinitions = gatewayPredicateDefinitionService.list(
                new QueryWrapper<GatewayPredicateDefinition>().lambda()
                        .eq(GatewayPredicateDefinition::getEnv, env)
                        .in(GatewayPredicateDefinition::getRouteId, routeIdList)
                        .eq(GatewayPredicateDefinition::getStatus, CommonStatusEnum.ONLINE.getStatus())
                        .eq(GatewayPredicateDefinition::getDeleted, DeletedEnum.NO.value)
        );
        if (CollectionUtils.isEmpty(gatewayPredicateDefinitions)) {
            return;
        }
        Map<String, List<GatewayFilterDefinition>> routeFilterMap = gatewayFilterDefinitions.stream().collect(Collectors.groupingBy(GatewayFilterDefinition::getRouteId));
        Map<String, List<GatewayPredicateDefinition>> routePredicateMap = gatewayPredicateDefinitions.stream().collect(Collectors.groupingBy(GatewayPredicateDefinition::getRouteId));

        for (GatewayRouteDefinition gatewayRouteDefinition : gatewayRouteDefinitions) {
            List<GatewayFilterDefinition> filterDefinitions = routeFilterMap.get(gatewayRouteDefinition.getRouteId());
            List<GatewayPredicateDefinition> predicateDefinitions = routePredicateMap.get(gatewayRouteDefinition.getRouteId());
            if (CollectionUtils.isEmpty(filterDefinitions) || CollectionUtils.isEmpty(predicateDefinitions)) {
                continue;
            }
            RouteData routeData = RouteDataConvertFunction.convert(gatewayRouteDefinition);
            routeData.setFilters(doRefreshRouteFilter(gatewayFilterDefinitions));
            routeData.setPredicates(doRefreshRoutePredicate(gatewayPredicateDefinitions));
            routeDataHandler.handle(routeData);
        }
    }

    public List<FilterData> doRefreshRouteFilter(List<GatewayFilterDefinition> filterDefinitions) {
        List<FilterData> filterDataList = Lists.newArrayList();
        for (GatewayFilterDefinition filterDefinition : filterDefinitions) {
            FilterData filterData = RouteDataConvertFunction.convert(filterDefinition);
            Optional.ofNullable(filterHandlerMap.get(filterData.getName()))
                    .ifPresent(routeFilterDataHandler -> routeFilterDataHandler.handle(filterData));
            filterDataList.add(filterData);
        }
        return filterDataList;
    }

    public List<PredicateData> doRefreshRoutePredicate(List<GatewayPredicateDefinition> predicateDefinitions) {
        List<PredicateData> predicateDataList = Lists.newArrayList();
        for (GatewayPredicateDefinition predicateDefinition : predicateDefinitions) {
            PredicateData predicateData = RouteDataConvertFunction.convert(predicateDefinition);
            Optional.ofNullable(predicateHandlerMap.get(predicateData.getName()))
                    .ifPresent(routePredicateDataHandler -> routePredicateDataHandler.handle(predicateData));
            predicateDataList.add(predicateData);
        }
        return predicateDataList;
    }
}
